/**
 *Swings.java
 *@anthor Daniel Liu
 *2006-5-10 ����01:11:10
 **/
package org.lex.swing;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.swing.AbstractButton;
import javax.swing.ActionMap;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

public class Swings {
	public static final String ERROR_ICON = "OptionPane.errorIcon";
	public static final String INFORMATION_ICON = "OptionPane.informationIcon";
	public static final String QUESTION_ICON = "OptionPane.questionIcon";
	public static final String WARNING_ICON = "OptionPane.warningIcon";
	public static final String DIRECTORY_ICON = "FileView.directoryIcon";
	public static final String FILE_ICON = "FileView.fileIcon";

	/***************************************************************************
	 * Utilities for create a Top Level(JDialog,JFrame) Container.<br>
	 * Useful in debugging, so i choose short handy names for them.
	 **************************************************************************/
	public static void show(final Component c) {
		show(c, true);
	}

	public static void show(final Component c, final boolean scroll) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				createWindow(c, scroll).setVisible(true);
			}
		});
	}

	public static Window createWindow(Component c) {
		return createWindow(c, true);
	}

	public static Window createWindow(Component c, boolean scroll) {
		Window w = null;
		if (c instanceof Window) {
			w = (Window) c;
		} else {
			JFrame frame = new JFrame();
			if (true == scroll && false == c instanceof JScrollPane)
				frame.setContentPane(new JScrollPane(c));
			else {
				Container con = null;
				if (c instanceof Container)
					con = (Container) c;
				else {
					con = new JPanel(new GridLayout(1, 1));
					con.add(c);
				}
				frame.setContentPane(con);
			}
			frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
			frame.setTitle(c.getClass().getSimpleName());
			w = frame;
		}
		w.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		// set window half size of screen
		Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
		w.setSize(screen.width / 2, screen.height / 2);
		// and move it to screen center
		w.setLocationRelativeTo(null);
		return w;
	}

	/***************************************************************************
	 * Collections of JButton utilities
	 **************************************************************************/
	public static <T extends AbstractButton> void setSelected(boolean b, T... buttons) {
		if (null == buttons || 0 == buttons.length)
			return;
		for (AbstractButton t : buttons)
			t.setSelected(b);
	}

	public static <T extends AbstractButton> ButtonGroup createButtonGroup(T... buttons) {
		ButtonGroup group = new ButtonGroup();
		if (null == buttons || 0 == buttons.length)
			return group;
		for (T b : buttons)
			if (null != b)
				group.add(b);
		return group;
	}

	public static void dumpInputActionMap(OutputStream out, JComponent c) {
		InputMap im = c.getInputMap();
		ActionMap am = c.getActionMap();
		PrintStream ps = new PrintStream(out, true);
		KeyStroke[] ks = im.keys();
		Object[] as = am.keys();
		ps.println(ks.length + " keys");
		ps.println(as.length + " actions");
		ps.format("%-10s%-10s", "Key", "Action");
	}

	/***************************************************************************
	 * Look and Feel Utilities
	 **************************************************************************/
	public static void setUseBoldMetalFont(boolean bold) {
		UIManager.put("swing.boldMetal", bold);
	}

	public static void setDefaultFont(Font font) {
		Swings.setDefaultFont(UIManager.getLookAndFeelDefaults(), font);
		Swings.setDefaultFont(UIManager.getDefaults(), font);
	}

	public static void setDefaultFont(UIDefaults d, Font font) {
		if (null == d || null == font)
			return;
		FontUIResource fui = new FontUIResource(font);
		Set<Object> keys = d.keySet();
		for (Object key : keys) {
			if (null != key && -1 != key.toString().indexOf("font")) {
				d.put(key, fui);
			}
		}
	}

	/**
	 * Construct LookAndFeel instance from given className without throw
	 * exception
	 * 
	 * @param className
	 * @return LookAndFeel or null
	 */
	public static LookAndFeel getLookAndFeel(String className) {
		/*
		 * get the look-and-feel instance from class-name. these codes mostly
		 * copied from UIManager.setLookAndFeel( String ) and
		 * SwingUtilities.loadSystemClass( String ) i don't get the real means
		 * of these codes ...
		 */
		try {
			Class<?> lnfClass = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
			return (LookAndFeel) lnfClass.newInstance();
		} catch (Exception e) {
		}
		return null;
	}

	/**
	 * Methods for setting LookAndFeel but don't throw exceptions
	 */
	public static boolean tryToUseSystemLookAndFeel() {
		return tryToUseLookAndFeel(UIManager.getSystemLookAndFeelClassName());
	}

	public static boolean tryToUseCrossPlatformLookAndFeel() {
		return tryToUseLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
	}

	public static boolean tryToUseLookAndFeel(LookAndFeel lnf) {
		try {
			UIManager.setLookAndFeel(lnf);
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	public static boolean tryToUseLookAndFeel(String lnfClassName) {
		try {
			UIManager.setLookAndFeel(lnfClassName);
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	/***************************************************************************
	 * Methods for updating Component LookAndFeel
	 **************************************************************************/
	public static void updateComponentTreeUI(Iterable<? extends Component> components) {
		Iterator<? extends Component> itor = components.iterator();
		while (itor.hasNext())
			updateComponentTreeUI(itor.next());
	}

	public static void updateComponentTreeUI(Component[] components) {
		for (int i = 0; i < components.length; i++)
			updateComponentTreeUI(components[i]);
	}

	public static void updateComponentTreeUI(Component c) {
		if (null == c)
			return;
		updateComponentTreeUI0(c);
		c.invalidate();
		c.validate();
		c.repaint();
	}

	private static void updateComponentTreeUI0(Component c) {
		if (c instanceof JComponent) {
			JComponent jc = (JComponent) c;
			jc.updateUI();
			JPopupMenu jpm = jc.getComponentPopupMenu();
			if (jpm != null
			// && jpm.isVisible()
					&& jpm.getInvoker() == jc) {
				updateComponentTreeUI(jpm);
			}
		}
		Component[] children = null;
		if (c instanceof JMenu) {
			children = ((JMenu) c).getMenuComponents();
		} else if (c instanceof Container) {
			children = ((Container) c).getComponents();
		}
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				updateComponentTreeUI0(children[i]);
			}
		}
	}

	/*
	 * we want to see the whole tree. These functions expand it for us, they are
	 * written by Christian Kaufhold and taken from the comp.lang.jave.help
	 * newsgroup
	 */
	public static void expandAll(JTree tree) {
		Object root = tree.getModel().getRoot();
		if (root != null)
			expandAll(tree, new TreePath(root));
	}

	public static void expandAll(JTree tree, TreePath path) {
		for (Iterator i = extremalPaths(tree.getModel(), path, new HashSet<TreePath>()).iterator(); i.hasNext();)
			tree.expandPath((TreePath) i.next());
	}

	/**
	 * The "extremal paths" of the tree model's subtree starting at path. The
	 * extremal paths are those paths that a) are non-leaves and b) have only
	 * leaf children, if any. It suffices to know these to know all non-leaf
	 * paths in the subtree, and those are the ones that matter for expansion
	 * (since the concept of expan- sion only applies to non-leaves). The
	 * extremal paths of a leaves is an empty collection. This method uses the
	 * usual collection filling idiom, i.e. clear and then fill the collection
	 * that it is given, and for convenience return it. The extremal paths are
	 * stored in the order in which they appear in pre-order in the tree model.
	 */
	public static Collection extremalPaths(TreeModel data, TreePath path, Collection<TreePath> result) {
		result.clear();
		if (data.isLeaf(path.getLastPathComponent())) {
			return result; // should really be forbidden (?)
		}
		extremalPathsImpl(data, path, result);
		return result;
	}

	private static void extremalPathsImpl(TreeModel data, TreePath path, Collection<TreePath> result) {
		Object node = path.getLastPathComponent();
		boolean hasNonLeafChildren = false;
		int count = data.getChildCount(node);
		for (int i = 0; i < count; i++)
			if (!data.isLeaf(data.getChild(node, i)))
				hasNonLeafChildren = true;
		if (!hasNonLeafChildren)
			result.add(path);
		else {
			for (int i = 0; i < count; i++) {
				Object child = data.getChild(node, i);
				if (!data.isLeaf(child))
					extremalPathsImpl(data, path.pathByAddingChild(child), result);
			}
		}
	}

	/***************************************************************************
	 * Common Icons provide by current LaF
	 **************************************************************************/
	public static Icon getIcon(String key) {
		return UIManager.getIcon(key);
	}

	public static Icon directoryIcon() {
		return UIManager.getIcon(DIRECTORY_ICON);
	}

	public static Icon errorIcon() {
		return UIManager.getIcon(ERROR_ICON);
	}

	public static Icon fileIcon() {
		return UIManager.getIcon(FILE_ICON);
	}

	public static Icon informationIcon() {
		return UIManager.getIcon(INFORMATION_ICON);
	}

	public static Icon questionIcon() {
		return UIManager.getIcon(QUESTION_ICON);
	}

	public static Icon warningIcon() {
		return UIManager.getIcon(WARNING_ICON);
	}

	private Swings() {
	}
}
